/////////////////////////////////////////////////////
/// @file sampler.h
///	    Some sampler implementations
///
/// @author Dmitry Kolzov
///
#ifndef sampler_H
#define sampler_H

#include <vector>
#include <algorithm>

#include "common_defs.h"
#include "utils.h"

#ifdef _DEBUG
#include <iostream>
#endif

#define DEFAULT_SAMPLE_COUNT 1
#define DEFAULT_SETS_COUNT 83

namespace core
{
	/// class representing a sampler object
	/// is capable of sampling unit square
	/// unit disk and hemisphere
	/// to declare a particular sampler all
	/// you should do is to override
	/// generate_samples() method
	class sampler
	{
	public:
		/// constructor
		sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT);

		/// return a sample in the unit square
		/// sampling is performed num_samples()
		/// times from a single sampling pattern
		vector2 sample_unit_square();

		/// return a sample in the unit disk
		/// the samples are generated by
		/// concentrically mapping unit square
		/// to unit disk
		/// before calling this method
		/// make sure map_samples_to_unit_disk
		/// has been called
		vector2 sample_unit_disk();

		/// return a sample on a unit hemisphere
		/// samples are distributed with
		/// cosine power law (the power
		/// is specified in map_samples call
		vector3 sample_unit_hemisphere();

		/// map samples to unit disk 
		/// using concetric map by Shirley
		void map_samples_to_unit_disk();

		/// map samples to unit hemisphere
		/// using a cosine power law
		void map_samples_to_unit_hemisphere( const REAL& e );

		/// accessors
		void set_num_samples( UINT numSamples ) { _numSamples = numSamples; }
		void set_num_sets   ( UINT numSets )    { _numSets = numSets; }
		UINT num_samples    () const            { return _numSamples; }
		UINT num_sets       () const            { return _numSets; }

	protected:
		/// generate num_samples() sample patterns
		virtual void	generate_samples(){}
		/// prepare shuffled indices
		void    setup_shuffled_indices();
		/// add sample to a samples array
		void    add_sample( const vector2& s ) { _samples.push_back(s); }
		/// access sample
		/// be careful, index checking is not performed
		vector2& get_sample( UINT index ) { return _samples[index]; }

	private:
		/// a number of samples in a pattern
		UINT _numSamples;
		/// a number of patterns
		UINT _numSets;
		/// random pattern offset
		UINT _jump;
		/// number of samples requested
		UINT _count;
		/// samples on unit square
		std::vector<vector2> _samples;
		/// samples on a unit disk
		std::vector<vector2> _diskSamples;
		/// samples on a hemisphere
		std::vector<vector3> _hemisphereSamples;
		/// shuffled sample indices
		std::vector<UINT> _shuffledIndices;
	};

	/// regular sampler
	class regular_sampler : public sampler
	{
	public:
		regular_sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT) : 
		  sampler(numSamples, numSets){generate_samples();}
	protected:
		virtual void generate_samples();
	};

	/// random sampler
	class random_sampler : public sampler
	{
	public:
		random_sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT) : 
		  sampler(numSamples, numSets){generate_samples();}
	protected:
		virtual void generate_samples();
	};

	/// jittered sampler
	class jittered_sampler : public sampler
	{
	public:
		jittered_sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT) : 
		  sampler(numSamples, numSets){generate_samples();}
	protected:
		virtual void generate_samples();
	};

	/// N-rooks sampler
	class nrooks_sampler : public sampler
	{
	public:
		nrooks_sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT) : 
		  sampler(numSamples, numSets){generate_samples();}

	protected:
		virtual void generate_samples();
		virtual void shuffle();
	};

	/// Hammerseley sampler
	class hammersley_sampler : public sampler
	{
	public:
		hammersley_sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT) : 
		  sampler(numSamples, numSets){generate_samples();}

	protected:
		virtual void generate_samples();
		REAL phi( UINT n );
	};

	/// multijittered sampler
	class multijittered_sampler : public nrooks_sampler
	{
	public:
		multijittered_sampler(UINT numSamples = DEFAULT_SAMPLE_COUNT, UINT numSets = DEFAULT_SETS_COUNT) : 
		  nrooks_sampler(numSamples, numSets){generate_samples();}

	protected:
		virtual void generate_samples();
	};
}

#endif